package com.smsc.headend.connector.builder.impl;

import cn.hutool.core.util.NumberUtil;
import com.smsc.headend.common.enums.errorcode.DeviceConnectErrorCode;
import com.smsc.headend.connector.bo.AssociationState;
import com.smsc.headend.connector.bo.UnwrappedPacket;
import com.smsc.headend.connector.builder.AssociationBuilder;
import com.smsc.headend.connector.builder.LoggerBuilder;
import com.smsc.headend.connector.exception.UDISDeviceConException;
import com.smsc.headend.connector.service.*;
import com.smsc.headend.connector.service.protocol.DLMSPacketCodecService;
import com.smsc.headend.connector.utils.Cosem46ByteUtil;
import com.smsc.headend.connector.utils.CosemXMLUtil;
import com.smsc.headend.connector.utils.LogFileNameUtil;
import com.smsc.headend.module.asset.dmo.DLMSConfigDMO;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.asset.entity.ComEntity;
import com.smsc.headend.module.asset.entity.PlcMeterStatus;
import gurux.dlms.*;
import gurux.dlms.enums.ExceptionServiceError;
import gurux.dlms.enums.InterfaceType;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

/**
 * @author liangli
 * @date 2020/8/30
 */
@Slf4j
public class DefaultAssociationBuilder implements AssociationBuilder {

    @Autowired
    TerminalChannelService terminalChannelService;
    @Autowired
    DeviceConnectService deviceConnectService;
    @Autowired
    DLMSPacketCodecService packetCodecService;
    @Value("${device.association.connection-timeout:60}")
    String connectionTimeout;
    @Autowired
    LogFileNameUtil logFileNameUtil;

    Integer connectionTimeoutSec;

    @Autowired
    DeviceStatusService deviceStatusService;
    @Autowired
    FeignAssetManagementService assetManagementService;

    private GXDLMSClient lowClient = new GXDLMSClient();

    @Override
    public byte[] nextPacket(ComEntity com, AssociationState associationState) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        Integer currentStep = associationState.getStepNumber();
        if (currentStep == null) {
            return getInitRequest(associationState);
        }
        if (currentStep == LLSAssociationBuilder.LLSState.snrmRequest.ordinal()) {
            associationState.setStepNumber(LLSAssociationBuilder.LLSState.llsCreate.ordinal());
            return associationState.getClient().aarqRequest()[0];
        }
        return null;
    }

    private byte[] getInitRequest(AssociationState associationState) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        boolean isHdlc = associationState.getClient().getInterfaceType().equals(InterfaceType.HDLC);
        if (isHdlc) {
            associationState.setStepNumber(LLSAssociationBuilder.LLSState.snrmRequest.ordinal());
            return associationState.getClient().snrmRequest();
        }
        associationState.setStepNumber(LLSAssociationBuilder.LLSState.llsCreate.ordinal());
        return associationState.getClient().aarqRequest()[0];
    }

    @Override
    public Boolean acceptPacket(ComEntity terminal, byte[] byteReceive, AssociationState associationState) throws UDISDeviceConException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        Integer currentStep = associationState.getStepNumber();
        Boolean hasNext = true;
        LLSAssociationBuilder.LLSState state = Arrays.stream(LLSAssociationBuilder.LLSState.values()).filter(s -> s.ordinal() == currentStep).findFirst().get();
        switch (state) {
            case snrmRequest:
                snrmResponse(byteReceive, associationState);
                break;
            case llsCreate:
                llsAssociationResponse(terminal, byteReceive, associationState);
                hasNext = false;
                break;
        }
        return hasNext;
    }

    private void snrmResponse(byte[] aare, AssociationState associationState) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        UnwrappedPacket packet = packetCodecService.unwrapPrefixPacket(aare);
        GXReplyData replyData = new GXReplyData();
        GXByteBuffer gxByteBuffer = new GXByteBuffer();
        gxByteBuffer.set(packet.getPacket());
        associationState.getClient().getData(gxByteBuffer, replyData);
        associationState.getClient().parseUAResponse(replyData.getData());
    }


    private void llsAssociationResponse(ComEntity terminal, byte[] bytes, AssociationState aaState) throws UDISDeviceConException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        UnwrappedPacket packet = packetCodecService.unwrapPrefixPacket(bytes);
        GXReplyData replyData = new GXReplyData();
        GXByteBuffer gxByteBuffer = new GXByteBuffer();
        gxByteBuffer.set(packet.getPacket());
        aaState.getClient().getData(gxByteBuffer, replyData);
        aaState.getClient().parseAareResponse(replyData.getData());
        if (aaState.getClient().getInterfaceType().equals(InterfaceType.HDLC)) {
            // 塞入frameType
            aaState.setFrameId(Cosem46ByteUtil.getFrameType(aaState.getClient(), packet.getPacket()));
        }
        // default目前只有dcu的需要更新状态
//        deviceStatusService.updateDeviceStatus(terminal, null);
//        deviceStatusService.updateComEntityStatus(terminal, ComEntity.CommunicationStatus.ONLINE.getCode());
//        deviceStatusService.updatePlcMeterStatus(meter, PlcMeterStatus.Status.Online.getCode());
    }
    @Override
    public void buildAssociation(ComEntity terminal, Meter meter, Channel channel, AssociationState aaState) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, UDISDeviceConException, InterruptedException {
        this.connectionTimeoutSec = NumberUtil.isNumber(connectionTimeout) ? Integer.parseInt(connectionTimeout) : 60;
        DLMSConfigDMO dlmsConfigDMO = packetCodecService.getDLMSConfigDMO(terminal, meter);
        if (dlmsConfigDMO != null && dlmsConfigDMO.getInterfaceType() .equalsIgnoreCase(InterfaceType.HDLC.name())) {
            // 46 HDLC需要发送snrm request
            sendSnrmRequest(terminal, meter, channel, aaState);
        }
        sendAarqRequest(terminal, meter, channel, aaState);
    }

    public void sendSnrmRequest(ComEntity terminal, Meter meter, Channel channel, AssociationState aaState) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, UDISDeviceConException, InterruptedException {
        // no need to set the limits currently
//        GXDLMSLimits gxdlmsLimits = aaState.getClient().getLimits();
//        gxdlmsLimits.setMaxInfoRX(1024);
//        gxdlmsLimits.setMaxInfoTX(1024);
//        gxdlmsLimits.setWindowSizeTX(1);
//        gxdlmsLimits.setWindowSizeRX(7);
        byte[] snrmRequest = aaState.getClient().snrmRequest();

        snrmRequest = packetCodecService.wrapPrefixToPacket(snrmRequest, terminal, aaState.getDeviceAddress());
        deviceConnectService.sendPacket2Channel(channel, snrmRequest);
        //wait 60 s for aare
        byte[] aare = terminalChannelService.getSyncResponse(channel, this.connectionTimeoutSec, aaState.getDeviceAddress());
        if (aare != null && aare.length > 0) {
            UnwrappedPacket packet = packetCodecService.unwrapPrefixPacket(aare);
            GXReplyData replyData = new GXReplyData();
            GXByteBuffer gxByteBuffer = new GXByteBuffer();
            gxByteBuffer.set(packet.getPacket());
            aaState.getClient().getData(gxByteBuffer, replyData);
            if (replyData.getData().size() == 0 ) {
                throw new UDISDeviceConException("build snrm request fail");
            }
            aaState.getClient().parseUAResponse(replyData.getData());
        } else {
            throw new UDISDeviceConException("build snrm request fail");
        }
    }

    public void sendAarqRequest(ComEntity terminal, Meter meter, Channel channel, AssociationState aaState) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, UDISDeviceConException, InterruptedException {
        byte[] aarq = aaState.getClient().aarqRequest()[0];
        aarq = packetCodecService.wrapPrefixToPacket(aarq, terminal, aaState.getDeviceAddress());
        deviceConnectService.sendPacket2Channel(channel, aarq);
        //wait 60 s for aare
        byte[] aare = terminalChannelService.getSyncResponse(channel, this.connectionTimeoutSec, aaState.getDeviceAddress());
        if (aare != null && aare.length > 0) {
            UnwrappedPacket packet = packetCodecService.unwrapPrefixPacket(aare);
            GXReplyData replyData = new GXReplyData();
            GXByteBuffer gxByteBuffer = new GXByteBuffer();
            gxByteBuffer.set(packet.getPacket());
            aaState.getClient().getData(gxByteBuffer, replyData);
            aaState.getClient().parseAareResponse(replyData.getData());
            if (aaState.getClient().getInterfaceType().equals(InterfaceType.HDLC)) {
                // 塞入frameType
                aaState.setFrameId(Cosem46ByteUtil.getFrameType(aaState.getClient(), packet.getPacket()));
            }
//            // default目前只有dcu的需要更新状态
//            deviceStatusService.updateDeviceStatus(terminal, null);
//            deviceStatusService.updateComEntityStatus(terminal, ComEntity.CommunicationStatus.ONLINE.getCode());
//            deviceStatusService.updatePlcMeterStatus(meter, PlcMeterStatus.Status.Online.getCode());
        } else {
            throw new UDISDeviceConException("build aa fail");
        }
    }

}
